package event;

import java.nio.BufferOverflowException;
import java.nio.BufferUnderflowException;
import java.nio.ByteBuffer;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Vector;

public class EventFormatManager {
	private static EventFormatManager sInstance = null;

	public static EventFormatManager instance() {
		if (sInstance == null)
			sInstance = new EventFormatManager();

		return sInstance;
	}

	// streamID -> vector of field definitions
	private HashMap<Long, Vector<DEFINES.CEPLEventDataFieldDefinition>> formats = null;
	private Vector<Long> streamIDs = null;

	private EventFormatManager() {
		init();
	}

	public void reset() {
		if (formats != null) {
			Iterator<Long> iterator = formats.keySet().iterator();
			while (iterator.hasNext()) {
				Long key = (Long) iterator.next();
				Vector<DEFINES.CEPLEventDataFieldDefinition> v = formats
						.get(key);
				v.clear();
			}
			formats.clear();
		}
		if (streamIDs != null)
			streamIDs.clear();

	}

	protected void finalize() {
		reset();
	}

	public void init() {
		reset();
		formats = new HashMap<Long, Vector<DEFINES.CEPLEventDataFieldDefinition>>();
		streamIDs = new Vector<Long>();
	}

	public boolean isValidStreamID(long streamID) {
		return this.formats.get(streamID) != null;
	}

	public Vector<Long> getValidStreamIDs() {
		return streamIDs;
	}

	public void addEventFormat(long streamID,
			Vector<DEFINES.CEPLEventDataFieldDefinition> format) {
		formats.remove(new Long(streamID));
		formats.put(new Long(streamID), format);
		streamIDs.add(new Long(streamID));
	}

	public Vector<DEFINES.CEPLEventDataFieldDefinition> getEventFormat(
			long streamID) {
		return this.formats.get(streamID);
	}

	public boolean isNumber(long streamID, String fieldName) {
		return getFieldValueType(streamID, fieldName) != DEFINES.CEPLEventDataFieldType.TEXT
				&& getFieldValueType(streamID, fieldName) != DEFINES.CEPLEventDataFieldType.UNKNOWN;
	}

	public boolean isText(long streamID, String fieldName) {
		return getFieldValueType(streamID, fieldName) == DEFINES.CEPLEventDataFieldType.TEXT;
	}

	public DEFINES.CEPLEventDataFieldType getFieldValueType(long streamID,
			String fieldName) {
		Long it = new Long(streamID);
		if (formats.get(it) != null) {
			Vector<DEFINES.CEPLEventDataFieldDefinition> format = formats
					.get(it);
			for (int i = 0; i < format.size(); ++i) {
				DEFINES.CEPLEventDataFieldDefinition def = format.get(i);
				if (fieldName.compareToIgnoreCase(def.fieldName) == 0) {
					if (!def.isNumerical)
						return DEFINES.CEPLEventDataFieldType.TEXT;
					if (def.isNumerical && def.isFloatingPoint
							&& def.lengthInBytes == 4)
						return DEFINES.CEPLEventDataFieldType.FLOAT;
					if (def.isNumerical && def.isFloatingPoint
							&& def.lengthInBytes == 8)
						return DEFINES.CEPLEventDataFieldType.DOUBLE;
					if (def.isNumerical && !def.isFloatingPoint
							&& def.lengthInBytes == 1)
						return DEFINES.CEPLEventDataFieldType.CHAR;
					if (def.isNumerical && !def.isFloatingPoint
							&& def.lengthInBytes == 2)
						return DEFINES.CEPLEventDataFieldType.SHORT;
					if (def.isNumerical && !def.isFloatingPoint
							&& def.lengthInBytes == 4)
						return DEFINES.CEPLEventDataFieldType.INT;
					if (def.isNumerical && !def.isFloatingPoint
							&& def.lengthInBytes == 8)
						return DEFINES.CEPLEventDataFieldType.LONG;
				}
			}
		}

		return DEFINES.CEPLEventDataFieldType.UNKNOWN;
	}

	public short getLengthInBytes(long streamID, String fieldName) {
		Long it = new Long(streamID);
		if (formats.get(it) != null) {
			Vector<DEFINES.CEPLEventDataFieldDefinition> format = formats
					.get(it);
			for (int i = 0; i < format.size(); ++i) {
				DEFINES.CEPLEventDataFieldDefinition def = format.get(i);
				if (fieldName.compareToIgnoreCase(def.fieldName) == 0) {
					return def.lengthInBytes;
				}
			}
		}

		return 0;
	}

	// used for deserialisation
	public HashMap<String, DEFINES.CEPLEventDataFieldValue> parseByteArray(
			long streamID, byte[] data) throws BufferUnderflowException,
			BufferOverflowException {

		try {
			HashMap<String, DEFINES.CEPLEventDataFieldValue> result = new HashMap<String, DEFINES.CEPLEventDataFieldValue>();
			int usedBytes = 0;
			ByteBuffer buff = ByteBuffer.wrap(data);

			Long it = new Long(streamID);
			if (formats.get(it) != null) {
				Vector<DEFINES.CEPLEventDataFieldDefinition> format = formats
						.get(it);
				for (int i = 0; i < format.size(); ++i) {
					DEFINES.CEPLEventDataFieldDefinition def = format.get(i);
					DEFINES.CEPLEventDataFieldValue fieldValue = new DEFINES.CEPLEventDataFieldValue();

					fieldValue.fieldName = def.fieldName;
					fieldValue.lengthInBytes = def.lengthInBytes;
					fieldValue.type = getFieldValueType(streamID, def.fieldName);
					Object value = null;

					if (fieldValue.type == DEFINES.CEPLEventDataFieldType.CHAR) {
						value = new Character(buff.getChar());
						usedBytes += 1;
					} else if (fieldValue.type == DEFINES.CEPLEventDataFieldType.SHORT) {
						value = new Short(buff.getShort());
						usedBytes += 2;
					} else if (fieldValue.type == DEFINES.CEPLEventDataFieldType.INT) {
						value = new Integer(buff.getInt());
						usedBytes += 4;
					} else if (fieldValue.type == DEFINES.CEPLEventDataFieldType.LONG) {
						value = new Long(buff.getLong());
						usedBytes += 8;
					} else if (fieldValue.type == DEFINES.CEPLEventDataFieldType.FLOAT) {
						value = new Float(buff.getFloat());
						usedBytes += 4;
					} else if (fieldValue.type == DEFINES.CEPLEventDataFieldType.DOUBLE) {
						value = new Double(buff.getDouble());
						usedBytes += 8;
					} else if (fieldValue.type == DEFINES.CEPLEventDataFieldType.TEXT) {
						short lengthInBytes = def.lengthInBytes;
						if (def.isPrecededByLengthInBytes) {
							lengthInBytes = buff.getShort();
							usedBytes += 2;
						}
						fieldValue.lengthInBytes = lengthInBytes;

						byte[] temp = new byte[lengthInBytes];
						buff.get(temp);
						String text = new String(temp);
						value = text;
						usedBytes += lengthInBytes;
					}

					fieldValue.value = value;
					result.put(def.fieldName, fieldValue);
				}
			}

			if (buff.hasRemaining())
				throw new BufferOverflowException();

			return result;
		} catch (BufferUnderflowException e) {
			e.printStackTrace();
			throw e;
		} catch (BufferOverflowException e) {
			throw e;
		}
	}
}
